package mth;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;

import org.w3c.dom.DOMImplementation;

import com.clarkparsia.pellet.sparqldl.jena.SparqlDLExecutionFactory;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.ModelFactory;

import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.swing.JSVGCanvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import org.w3c.dom.svg.SVGDocument;

import mth.Node;

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//BE CAREFUL: the program uses the numbers of the tasks to compare them      !
//so: if you use a weird numbering, probably there will be no useful output  !
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
public class ExampleQueries {

    private static final String ONTOLOGY = "sem/OntologyALB.owl";
    //the easy one: 0_0_0_ID151.owl
    //small average :) : 0_0_2_ID243.owl
    //the average one: 0_2_0_ID5.owl
    //the very hard one: 0_0_20_ID192.owl
    private static final String SOURCE_MODEL = "sem/0_0_20_ID192.owl";//0_7_0_ID175.owl";
    
    private Node[] nArr;
    private Node[][] stages=new Node[1][];
    
    //parameters for drawing
    private static int size=10;
    
  	private int canvasHeight = 1500;
  	private int canvasWidth = 1500; 
//  	//probably, sometimes, eventually, ... I will reactivate the borders...
//  	private double borders = 0.10;
  	private Font font = new Font("Arial",0,size);
  	
  	private void addStage() {
  		Node[][] helper = new Node[stages.length][];
  		for (int a=0; a<stages.length; a++) {
  			helper[a] = stages[a];
  		}
  		stages=new Node[helper.length+1][];
  		for (int a=0; a<helper.length; a++) {
  			stages[a]=helper[a];
  		}
  	}
  	
  	private void addNodeToStage(Node n, int s) {
  		if (s>stages.length-1) {
  			addStage();
  		}
  		n.setStage(s);
  		if (stages[s]==null || stages[s].length==0)
  			stages[s] = new Node[0];
  		Node[] helper = new Node[stages[s].length+1];
  		for (int a=0; a<stages[s].length; a++) {
  			helper[a]=stages[s][a];
  		}
  		helper[stages[s].length] = n;
  		stages[s] = new Node[helper.length];
  		for (int a=0; a<helper.length; a++) {
  			stages[s][a]=helper[a];
  		}
  	}
  	
  	private void nodeChangesStageTo(Node n, int s) {
  		if (stageContainsNode(n, s)) {
//  			System.out.println("Ein Wechsel, ohne tieferen Sinn.");
  			return;
  		}
  		//remove from old stage
  		Node[] helper = new Node[stages[n.getStage()].length-1];
  		boolean hit=false;
  		for (int a=0; a<stages[n.getStage()].length; a++) {
  			if (hit) {
  				helper[a-1]=stages[n.getStage()][a];
  			} else {
  				if (stages[n.getStage()][a]!=n && a<helper.length)
  					helper[a]=stages[n.getStage()][a];
  				else
  					hit=true;
  			}
  		}
  		//add to new stage
  		addNodeToStage(n, s);
  	}
  	
  	private void calculateStagesByPredecessors() {
  		for (int a=0; a<nArr.length; a++) {
  			if (nArr[a].getPredecessorEdgeCount()==0) {
  				nArr[a].setStage(0);
  				addNodeToStage(nArr[a], 0);
  			} else if (nArr[a].getPredecessorEdgeCount()==1) {
  				nArr[a].setStage(nArr[a].getPredecessors()[0].getStage()+1);
  				addNodeToStage(nArr[a], nArr[a].getPredecessors()[0].getStage()+1);
  			} else {
				nArr[a].setStage(nArr[a].getBiggestPredecessor(nArr[a].getPredecessors()).getStage()+1);
				addNodeToStage(nArr[a], nArr[a].getBiggestPredecessor(nArr[a].getPredecessors()).getStage()+1);
  			}
  		}
  		//because there can be wrong assignments, we need a correction for that
  		for (int a=0; a<nArr.length; a++) {
  			for (int b=0; b<nArr[a].getPredecessorEdgeCount(); b++) {
  				if (nArr[a].getStage()==nArr[a].getPredecessors()[b].getStage())
  					nodeChangesStageTo(nArr[a], nArr[a].getPredecessors()[b].getStage()+1);
  			}
  		}
  	}
  	
  	private boolean stageContainsNode (Node n, int s) {
  		if (s==stages.length)
  			return false;
  		for (int a=0; a<stages[s].length; a++) {
  			if (stages[s][a]==n)
  				return true;
  		}
  		return false;
  	}
  	
  	private void normStage(int s, int testi) {
  		//normalize sum of bounds per stage to the canvas height
  		//we don't need to watch for overlapping or equal bounds, because of
  		//the call out of the sortStage method, which will solve this problem
  		//so it does not matter which of the bounds will be moved 
  		//--> we use the lower bound because the upper bound can be 0
  		for (int a=0; a<stages[s].length; a++) {
  			stages[s][a].setLowerBound((int)(stages[s][a].getUpperBound()+(((double)canvasHeight*(double)(stages[s][a].getLowerBound()-stages[s][a].getUpperBound()))/(double)testi)));
  		}
  		//following we check, if there is further normalization needed
  		int testi2 = 0; 
  		for (int a=0; a<stages[s].length; a++) {
  			testi2+=stages[s][a].getLowerBound()-stages[s][a].getUpperBound();
  		}
  		//and to avoid an infinite loop, we check for testi==testi2
  		if (!(testi==testi2) && testi2<canvasHeight*0.9 && testi2>canvasHeight*0.7) {
  			normStage(s, testi2);
  		}
  	}

  	private int[] getSmallestMeanDeviation(int s) {
  		int[][] permutation = new int[3][stages[s].length];
  		for (int a=0; a<stages[s].length; a++) {
  			permutation[0][a]=a;
  			permutation[1][a]=stages[s][a].getNumber();
  			permutation[2][a]=stages[s][a].getY();
  		}
  		
  		int border = permutation[0].length-1;
  		while (border>0) {
			for (int a=0; a<border; a++) {
				if (permutation[2][a]>permutation[2][a+1]) {
					int helper=0;
					helper=permutation[0][a];
					permutation[0][a]=permutation[0][a+1];
					permutation[0][a+1]=helper;
					
					helper=permutation[1][a];
					permutation[1][a]=permutation[1][a+1];
					permutation[1][a+1]=helper;
					
					helper=permutation[2][a];
					permutation[2][a]=permutation[2][a+1];
					permutation[2][a+1]=helper;
				}
			}
			border--;
  		}
  		
  		return permutation[0];
  	}
  	
  	private void sortStage(int s) {
  	//check if Bound-Spaces of all stage nodes == canvasHeight
	//if yes, try to arrange the areas so, that every is most next to its calculated bounds
	//set the new bounds
  		int testi = 0;
  		for (int a=0; a<stages[s].length; a++) {
  			testi+=stages[s][a].getLowerBound()-stages[s][a].getUpperBound();
  		}
  		if (testi>canvasHeight || testi<canvasHeight*0.8) {
  			normStage(s, testi);
  		}
  		//OK...it's (clearly) not the smartest way, but i think the following way will serve good results:
  		//go through all possible alternatives for ordering (n!) per stage (s*n!) and choose the one
  		//for every stage, where the deviation of the means from the previous calculated Y values 
  		//is the smallest
  		
  		int[] orderInStage = new int[stages[s].length];
  		orderInStage = getSmallestMeanDeviation(s);
  		int ubHelper=0;
		int lbHelper=0;
  		for (int a=0; a<stages[s].length; a++) {
			ubHelper=stages[s][orderInStage[a]].getUpperBound();
			lbHelper=stages[s][orderInStage[a]].getLowerBound();
			if (a==0) {
				stages[s][orderInStage[a]].setUpperBound(0);
				stages[s][orderInStage[a]].setLowerBound(lbHelper-ubHelper);
			} else {
				stages[s][orderInStage[a]].setUpperBound(stages[s][orderInStage[a-1]].getLowerBound());
				stages[s][orderInStage[a]].setLowerBound(stages[s][orderInStage[a]].getUpperBound()+(lbHelper-ubHelper));
			}
  		}
  	}
  	
  	private void updateBoundsForStageWithOneNode(int s) {
  		if (stages[s].length==1) {
  			stages[s][0].setLowerBound(canvasHeight);
  			stages[s][0].setUpperBound(0);
  		}
  	}
  	
  	private void updateBoundsForFirstStage(int s) {
  		if (stages[s].length==1) {
  			updateBoundsForStageWithOneNode(s);
  		} else {
	  		if (s==0) {
	  			int[] ownSuccessorsInNextStage = new int[stages[s].length];
	  			int all = 0;
	  			for (int a=0; a<stages[s].length; a++) {
	  				for (int b=0; b<stages[s][a].getSuccessorEdgeCount(); b++) {
	  					if (stageContainsNode(stages[s][a].getSuccessors()[b],s+1)) {
	  						ownSuccessorsInNextStage[a]++;
	  						all++;
	  					}
	  				}
	  			}
				for (int a=0; a<stages[s].length; a++) {
					if (a==0) {
						stages[s][a].setLowerBound(canvasHeight);
						if (stages[s].length==1) {
							stages[s][a].setUpperBound(0);
						} else {
							stages[s][a].setUpperBound((int)(canvasHeight-(((double)1/(double)stages[s].length)*canvasHeight)));
						}
					} else {
						stages[s][a].setLowerBound((int)(stages[s][a-1].getUpperBound()));
						if (all==0) {
							stages[s][a].setUpperBound((int)(stages[s][a].getLowerBound()-(((double)1/(double)stages[s].length))*canvasHeight));
						} else {
							stages[s][a].setUpperBound((int)(stages[s][a].getLowerBound()-(((double)ownSuccessorsInNextStage[a]/(double)all)*canvasHeight)));
						}
					}
				}
	  		}
  		}
  	}
  	
  	
  	private void updateBounds(int s) {
  		if (s==0) {
  			updateBoundsForFirstStage(s);
  		} else if (stages[s].length==1) {
  			updateBoundsForStageWithOneNode(s);
  		} else {
  			//first: get the number of predecessors in the prevStage and the
  			//number of successors in the next stage
//  			int[] predInPrevS = new int[stages[s].length];
  			int[] centerCalculator= new int [stages[s].length];
  			int[] succInNextS = new int[stages[s].length];
  			
  			for (int a=0; a<stages[s].length; a++) {
  				for (int b=0; b<stages[s][a].getPredecessorEdgeCount(); b++) {
//		  				//with the if-condition we use only the direct predecessors
//  					if (stageContainsNode(stages[s][a].getPredecessors()[b], s-1)) { 
//						predInPrevS[a]++;
						centerCalculator[a]+=stages[s][a].getPredecessors()[b].getY();//*((double)1/((double)s-stages[s][a].getPredecessors()[b].getStage()));
//  					}
  				}
  				for (int b=0; b<stages[s][a].getSuccessorEdgeCount(); b++) {
  					if (stageContainsNode(stages[s][a].getSuccessors()[b], s+1)) {
  						succInNextS[a]++;
  					}
  				}
  				//after the following LOC every node contains as its own Y value
  				//the mean of the Y values of each predecessors out of the previous stage

  				//stages[s][a].setY((int)(((double)predInPrevS[a]*(double)centerCalculator[a]/(double)stages[s][a].getPredecessorEdgeCount())));
  				
  				stages[s][a].setY((int) ((double)centerCalculator[a]/(double)stages[s][a].getPredecessorEdgeCount()));
  				//now we calculate the bound-space and the lowerBounds with the help of the successors
  				if (stages.length==s+1) {
  					//we reached the last stage
  					stages[s][a].setUpperBound(stages[s][a].getY()-(int)((double)(canvasHeight)/(double)stages[s].length)/2);
  					stages[s][a].setLowerBound(stages[s][a].getY()+(int)((double)(canvasHeight)/(double)stages[s].length)/2);
  				} else {
//  					//probably the proportional way is not the best
//  					stages[s][a].setUpperBound(stages[s][a].getY()-(int)((double)(succInNextS[a]*canvasHeight)/(double)stages[s+1].length)/2);
//  					stages[s][a].setLowerBound(stages[s][a].getUpperBound()+(int)((double)(succInNextS[a]*canvasHeight)/(double)stages[s+1].length)/2);
  					stages[s][a].setUpperBound(stages[s][a].getY()-(int)((double)(canvasHeight)/(double)stages[s].length)/2);
  					stages[s][a].setLowerBound(stages[s][a].getUpperBound()+(int)((double)(canvasHeight)/(double)stages[s].length)/2);
  				}
  			}
  			//now comes the tricky part: we have to rearrange the bounds in a way, that no 
  			//bounds will overlap
  			//if (sumOfBounds~canvasHeight) ???
  			sortStage(s);
  		}
  		//the following for-loop even is important for the last branch of the if-else condition
  		//(for the first two it it obvious to see)
  		//because the set Y values can be obsolete after the sortStages(s) method call!
  		for (int a=0; a<stages[s].length; a++) {
  			stages[s][a].setXY((int)(stages[s][a].getStage()*canvasWidth/stages.length), (int)((double)(stages[s][a].getLowerBound()+stages[s][a].getUpperBound())/2));//-(int)((double)canvasHeight*0.2));
  		}
  		int testi = 0;
  		for (int a=0; a<stages[s].length; a++) {
  			testi+=stages[s][a].getLowerBound()-stages[s][a].getUpperBound();
  		}
  		if (testi>canvasHeight || testi<canvasHeight*0.8)
  			normStage(s, testi);
  	}
  	
  	//I assume, that every time this method is called you want to rebuild the stages or
  	//there was no previous call --> so there is no check for "is there a stage yet?"
  	//
  	//As a second I assume, that the Array.sort method works properly, so the first Nodes
  	//in nArr are the ones that should be at the left side of the canvas.
  	private void buildStages() {
  		calculateStagesByPredecessors();
  	}
  	
  	public void setSize(int s) {
  		size = s;
  	}
  	
  	public void setCanvasHeight(int h) {
  		canvasHeight = h;
  	}
  	
  	public void setCanvasWidth (int w) {
  		canvasWidth = w;
  	}
  	
//  	public void setBorders (double b) {
//  		borders = b;
//  	}
  	
  	public void setFont (Font f) {
  		font = f;
  	}
  	
  	public boolean nArrHasElement (Node n) {
  		for (int a=0; nArr!=null && a<nArr.length; a++)
  			if (nArr[a]==n)
  				return true;
  		return false;
  	}
  	
  	public boolean nArrHasElement (int i) {
  		for (int a=0; nArr!=null && a<nArr.length; a++)
  			if (nArr[a].getNumber()==i)
  				return true;
  		return false;
  	}
  	public void nArrAddElement (int e) {
  		if (!nArrHasElement(e)) {
	  		if (nArr==null || nArr.length==0) {
	  			nArr = new Node[1];
	  			nArr[0] = new Node(e);
	  		} else {
	  			Node []helper=new Node[nArr.length+1];
				for (int a=0; a<nArr.length; a++) {
					helper[a]=nArr[a];
				}
				helper[nArr.length]=new Node(e);
				nArr=new Node[helper.length];
				for (int b=0; b<helper.length; b++) {
					nArr[b]=helper[b];
				}
	  		}
  		} else {
//  			//probably sometimes there will be an alternative
//  			System.out.println("Element "+e+" existiert bereits");
  		}
  	}
  	
  	public void nArrAddElement (Node n) {
  		if (!nArrHasElement(n)) {
	  		if (nArr==null || nArr.length==0) {
	  			nArr = new Node[1];
	  			nArr[0] = n;
	  		} else {
	  			Node []helper=new Node[nArr.length+1];
				for (int a=0; a<nArr.length; a++) {
					helper[a]=nArr[a];
				}
				helper[nArr.length] = n;
				nArr=new Node[helper.length];
				for (int b=0; b<helper.length; b++) {
					nArr[b]=helper[b];
				}
	  		}
  		} else {
//			//probably sometimes there will be an alternative
//  			System.out.println("Element "+n.getNumber()+" existiert bereits");
  		}
  	}
  	
  	//I zero out the stages Member to be sure, that there is no garbage in it from an earlier call.
  	//To complete the zero-out the first for-loop deletes all stage-numbers of the nodes
    public void updateXY () {
  		stages = new Node[1][];
    	for (int a=0; a<nArr.length; a++) {
    		nArr[a].setStage(-1);
    	}
  		buildStages();
    	
  		for (int a=0; a<stages.length; a++) {
  			updateBounds(a);
  		}
//  		//commented because of updating X and Y while calculating the bounds for every stage
//  		for (int a=0; a<nArr.length; a++) {
//  			nArr[a].setXY((int)(nArr[a].getStage()*canvasWidth/stages.length), (int)((double)(nArr[a].getLowerBound()+nArr[a].getUpperBound())/2));
//  		}
    }
    
    public static void main(String[] args) throws IOException {
    	ExampleQueries exq = new ExampleQueries();
		// Loading the Ontology
		OntModel semanticModel = ModelFactory.createOntologyModel();
			//.createOntologyModel(PelletReasonerFactory.THE_SPEC);
//		System.out.println("Loading the model");
		InputStream input1 = new FileInputStream(ONTOLOGY);
		semanticModel.read(input1, null);
		input1.close();
//		InputStream input2 = new FileInputStream(SOURCE_MODEL);
		
		//-------------------------------------------------------------------
		//by activating this code, it is possible to generate all svg-files
		//during one run of the program
		//contains: reader, allFilesSep, for-loop with globalLoop-counter, allFilesInput
		//-------------------------------------------------------------------
		BufferedReader reader = new BufferedReader(new FileReader("sems.txt"));
		String[] allFilesSep=reader.readLine().split(",");
//		System.out.println(allFilesSep.length);
		
		for (int globalLoop=0; globalLoop<allFilesSep.length; globalLoop++) {
			InputStream allFilesInput = new FileInputStream("sem/"+allFilesSep[globalLoop]);
			semanticModel.read(allFilesInput, null, "N-TRIPLE");
			
//			semanticModel.read(input2, null, "N-TRIPLE");
//			input2.close();
			semanticModel.prepare();
		
			//semanticModel.write(System.out);
		
			// Query Execution 1
			//Select the tasks
			Query query = QueryFactory.read("./sparql/SelectTasks.sparql");
			QueryExecution qexec = SparqlDLExecutionFactory.create(query,
				semanticModel);
			ResultSet results = qexec.execSelect();
			
			while (results.hasNext()) {
			    QuerySolution qs = results.nextSolution();
			    //System.out.println(qs.toString());
			    String taskID = qs.getLiteral("id").getString();
			    //System.out.println("Task:" + taskID);
			    exq.nArrAddElement(Integer.parseInt(taskID));
			}
		
			// Query Execution 1
			// Select the predecessors
			Query query2 = QueryFactory
				.read("./sparql/SelectTaskSuccessors.sparql");
			QueryExecution qexec2 = SparqlDLExecutionFactory.create(query2,
				semanticModel);
			ResultSet results2 = qexec2.execSelect();
			
			while (results2.hasNext()) {
			    QuerySolution qs = results2.nextSolution();
			    //System.out.println(qs.toString());
			    String task1 = qs.getLiteral("id").getString();
			    if (!(qs.getLiteral("sucid") == null)) {
					String task2 = qs.getLiteral("sucid").getString();
			
//					System.out.println("Task " + task1 + " is a predecessor of task "
//						+ task2);
					for (int a=0; a<exq.nArr.length; a++) {
						if (exq.nArr[a].getNumber()==Integer.parseInt(task1))
							for (int b=0; b<exq.nArr.length; b++)
								if (exq.nArr[b].getNumber()==Integer.parseInt(task2))
									exq.nArr[a].setSuccessor(exq.nArr[b]);
		
						if (exq.nArr[a].getNumber()==Integer.parseInt(task2))
							for (int b=0; b<exq.nArr.length; b++)
								if (exq.nArr[b].getNumber()==Integer.parseInt(task1))
									exq.nArr[a].setPredecessor(exq.nArr[b]);
					}
			    }
			}
			
			//sorting of the Node[] Array...
			//and creating the neighborhood relations
			//neighborhood calculation based on the changes of the compareTo-method
			//
			//Mention: the actual implementation does not use the neighborhood approach!
			//but probably it will be useful for further development...?...
//			System.out.println("watching for neighbors...");
			java.util.Arrays.sort(exq.nArr);
			
			//update the X and Y member for each node
			exq.updateXY();
			
			//SVG File Generieren
			//z.B. mit http://xmlgraphics.apache.org/batik/index.html
			JSVGCanvas canvas = new JSVGCanvas();
			DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
			String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
			SVGDocument doc = (SVGDocument) impl.createDocument(svgNS, "svg", null);
			SVGGraphics2D g = new SVGGraphics2D(doc);
			g.setFont(exq.font);
			
			
			for (int a=0; a<exq.nArr.length;a++){
				Shape circle = new Ellipse2D.Double(exq.nArr[a].getX(), exq.nArr[a].getY()+50, 2*size, 2*size);
			    g.setPaint(Color.red);
			    g.draw(circle);
			    char[] x = new char[Integer.toString(exq.nArr[a].getNumber()).toCharArray().length];
			    x = Integer.toString(exq.nArr[a].getNumber()).toCharArray();
			    g.drawChars(x, 0, x.length, exq.nArr[a].getX(), exq.nArr[a].getY()+90);
			    if (exq.nArr[a].getSuccessorEdgeCount()>0) {
			    	for (int b=0; b<exq.nArr[a].getSuccessorEdgeCount(); b++) {
			    		g.drawLine(exq.nArr[a].getX(), exq.nArr[a].getY()+50, exq.nArr[a].getSuccessors()[b].getX(), exq.nArr[a].getSuccessors()[b].getY()+50);
			    	}
			    }
	//		    char[] lb = new char[Integer.toString(exq.nArr[a].getLowerBound()).toCharArray().length+3];
	//		    lb = (Integer.toString(exq.nArr[a].getNumber())+"L"+Integer.toString(exq.nArr[a].getLowerBound())).toCharArray();
	//		    g.drawChars(lb, 0, lb.length, exq.nArr[a].getX(), exq.nArr[a].getLowerBound()+70);
	//		    char[] ub = new char[Integer.toString(exq.nArr[a].getUpperBound()).toCharArray().length+3];
	//		    ub = (Integer.toString(exq.nArr[a].getNumber())+"U"+Integer.toString(exq.nArr[a].getUpperBound())).toCharArray();
	//		    g.drawChars(ub, 0, ub.length, exq.nArr[a].getX(), exq.nArr[a].getUpperBound()+50);
			}
	//		//The following block is for checking the system of the coordinates.
	//		//Here we draw the 'o' for zero and 'x' for the maximum canvas span of one dimension.
	//		char[] x = new char[2];
	//		x[0]='o';
	//		x[1]='o';
	//		g.drawChars(x, 0, 2, 0,10);
	//		
	//		x[0]='x';
	//		x[1]='o';
	//		g.drawChars(x, 0, 2, exq.canvasWidth,10);
	//		
	//		x[0]='o';
	//		x[1]='x';
	//		g.drawChars(x, 0, 2, 0,exq.canvasHeight);
	//		
	//		x[0]='x';
	//		x[1]='x';
	//		g.drawChars(x, 0, 2, exq.canvasWidth,exq.canvasHeight);
			
			//BEGIN:write SVG to file
			FileWriter fileWriter = new FileWriter(allFilesSep[globalLoop]+".svg");
		    g.stream(fileWriter, false);
			//END:finish SVG
		    System.out.println(globalLoop);
	    }
    }
}
